# ~*~ coding: utf-8 ~*~
import time

from flask import current_app
from sqlalchemy.dialects.mysql import TINYINT
from celery import current_task

from apps.common.dbbase import BaseModel
from apps.common.dbtypes import JsonDictTextField, JsonListTextField, EncryptJsonListTextField, JsonDictMediumTextField
from apps.common.utils import get_signer, generate_uuid, timestamp_obj_to_str
from apps.extensions import db
from ..ansible import AdHocRunner, AnsibleError
from ..inventory import BeesInventory

__all__ = [
    'Task', 'AdHoc', 'AdHocRunHistory'
]

signer = get_signer()


class Task(BaseModel):
    """
    This task is different ansible task, Task like 'push system user', 'get asset info' ..
    One task can have some versions of adhoc, run a task only run the latest version adhoc
    """
    __tablename__ = 'ops_task'

    id = db.Column(db.CHAR(36), default=generate_uuid, primary_key=True)
    name = db.Column(db.VARCHAR(128), unique=True)
    comment = db.Column(db.TEXT, nullable=True, default='')
    create_by = db.Column(db.VARCHAR(128), default='')
    is_deleted = db.Column(TINYINT, default=0)
    create_time = db.Column(db.INT, default=time.time)

    __latest_adhoc = None

    @property
    def short_id(self):
        return str(self.id).split('-')[-1]

    @property
    def latest_adhoc(self):
        if not self.__latest_adhoc:
            self.__latest_adhoc = self.get_latest_adhoc()
        return self.__latest_adhoc

    def get_latest_adhoc(self):
        latest_adhoc = db.session.query(AdHoc). \
            filter(AdHoc.task_id == self.id). \
            order_by(AdHoc.create_time.desc()). \
            first()
        return latest_adhoc

    @latest_adhoc.setter
    def latest_adhoc(self, item):
        self.__latest_adhoc = item

    @property
    def latest_history(self):
        return db.session.query(AdHocRunHistory).\
            filter(AdHocRunHistory.task_id == self.id). \
            order_by(AdHocRunHistory.date_start.desc()).\
            first()

    @property
    def history_summary(self):
        history = self.get_run_history()
        total = len(history)
        success = len([history for history in history if history.is_success])
        failed = len([history for history in history if not history.is_success])
        return {'total': total, 'success': success, 'failed': failed}

    def get_run_history(self):
        return db.session.query(AdHocRunHistory).filter(AdHocRunHistory.task_id == self.id).all()

    def run(self, record=True):
        if self.latest_adhoc:
            return self.latest_adhoc.run(record=record)
        else:
            return {'error': 'No adhoc'}


class AdHoc(BaseModel):
    """
    task_id: A task reference
    tasks: [{'name': 'task_name', 'action': {'module': '', 'args': ''}, 'other..': ''}, ]
    hosts: [{"hostname":"testserver", "ip": 192.169.1.111}, ], hostname must be unique key of cmdb
    become: May be using become [sudo, su] options. [{method: "sudo", user: "user", pass: "pass"], ]
    run_as: [{"username": "", "password": "", "private_key": ""}, ]
    options: ansible options, more see ops.ansible.runner.Options [{}, ]
    pattern: Even if we set _hosts, We only use that to make inventory, We also can set `patter` to run task on match hosts
    """
    __tablename__ = 'ops_adhoc'

    id = db.Column(db.CHAR(36), default=generate_uuid, primary_key=True)
    task_id = db.Column(db.CHAR(36))
    tasks = db.Column(JsonListTextField)
    hosts = db.Column(JsonListTextField)
    become = db.Column(EncryptJsonListTextField)
    run_as = db.Column(EncryptJsonListTextField)
    pattern = db.Column(db.VARCHAR(128))
    options = db.Column(JsonDictTextField)
    run_as_admin = db.Column(db.BOOLEAN)
    create_time = db.Column(db.INT, default=time.time)

    @property
    def task(self):
        return db.session.query(Task).get(self.task_id)

    @property
    def short_id(self):
        return str(self.id).split('-')[-1]

    @property
    def latest_history(self):
        return db.session.query(AdHocRunHistory). \
            filter(AdHocRunHistory.adhoc_id == self.id). \
            order_by(AdHocRunHistory.date_start.desc()). \
            first()

    @property
    def inventory(self):
        inventory = BeesInventory(
            self.hosts, run_as=self.run_as, become=self.become, run_as_admin=self.run_as_admin
        )
        return inventory

    def run(self, record=True):
        if record:
            return self._run_and_record()
        else:
            return self._run_only()

    def _run_and_record(self):
        history = AdHocRunHistory()
        try:
            cid = current_task.request.id
        except AttributeError:
            cid = generate_uuid()
        history.celery_id = cid
        history.adhoc_id = self.id
        history.task_id = self.task_id

        time_start = time.time()
        history.date_start = time_start
        history.save()
        time_end = None
        try:
            print("{} Start task: {}".format(timestamp_obj_to_str(time_start), self.task.name))
            raw, summary = self._run_only()
            time_end = time.time()
            print("{} Task finish".format(time_end))

            history.is_finished = True
            if summary.get('dark'):
                history.is_success = False
            else:
                history.is_success = True
            history.result = raw
            history.summary = summary
            return raw, summary
        except Exception as e:
            return {}, {"dark": {"all": str(e)}, "contacted": []}
        finally:
            history.date_finished = time_end
            history.timedelta = time_end - time_start
            history.update()

    def _run_only(self):
        runner = AdHocRunner(self.inventory, options=self.options)
        try:
            result = runner.run(
                self.tasks,     # [{"action": {"module": "shell", "args": "ls"}, "name": "run_cmd"},]
                self.pattern,   # all
                self.task.name,
            )
            return result.results_raw, result.results_summary
        except AnsibleError as e:
            current_app.logger.error("Ansible runner error : {}".format(repr(e)))

    def __str__(self):
        return "{} of {}".format(self.task.name, self.short_id)

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return False

        fields_check = ['task_id', 'tasks', 'options', 'hosts', 'become', 'run_as', 'pattern']
        for field in fields_check:
            if getattr(self, field) != getattr(other, field):
                return False
        return True


class AdHocRunHistory(BaseModel):
    """
    AdHoc running history.
    """
    __tablename__ = 'ops_adhoc_history'

    id = db.Column(db.CHAR(36), default=generate_uuid, primary_key=True)

    task_id = db.Column(db.CHAR(36))
    adhoc_id = db.Column(db.CHAR(36))
    celery_id = db.Column(db.CHAR(36))
    date_start = db.Column(db.INT, nullable=True)
    date_finished = db.Column(db.INT, nullable=True)
    timedelta = db.Column(db.INT, default=0, nullable=True)
    is_finished = db.Column(TINYINT, default=2)
    is_success = db.Column(TINYINT, default=2)

    result = db.Column(JsonDictMediumTextField, nullable=True, default={})
    summary = db.Column(JsonDictTextField, default={"ok": {}, "dark": {}}, nullable=True)

    @property
    def short_id(self):
        return str(self.id).split('-')[-1]

    @property
    def success_hosts(self):
        return self.summary.get('contacted', {})

    @property
    def failed_hosts(self):
        return self.summary.get('dark', {})

    def __str__(self):
        return self.short_id
